#include "nnsliteheapfile.h"
#include "nnslitememory.h"

/* Definitions of macros to access the code of page state */
#define NNSLITE_PAGE_SET_USED(state)    ((state) |= 0x80000000)
#define NNSLITE_PAGE_SET_NOTUSED(state) ((state) &= 0x7FFFFFFF)
#define NNSLITE_PAGE_SET_NEXT_NULL(state) ((state) &= 0x80000000)
#define NNSLITE_PAGE_SET_NEXTPAGE(state,next) ((state) = 0x80000000|((next)+1))
#define NNSLITE_PAGE_NEXT_PAGE(state)  (((state) & 0x7FFFFFFF) - 1)
#define NNSLITE_PAGE_IS_USED(state)  (((state) & 0x80000000) == 0x80000000)
#define NNSLITE_PAGE_HAS_NEXT(state) (((state) & 0x7FFFFFFF) != 0x0)


static NNSLITE_RET FindFreePage(nnsliteHeapFS* heapFS, u32* pageindex);
static NNSLITE_RET PageWrite(nnsliteHeapFS* heapFS, u32 pageIndex, u32 offset, const void* buf, u32 size);
static u32 PageRead(nnsliteHeapFS* heapFS, u32 pageIndex, u32 offset, void* buf, u32 size);
static void PageZero(nnsliteHeapFS* heapFS, u32 pageIndex);
static u32 GetPageIndex(nnsliteHeapFile* heapFile, u32 pageIndexOfFile);

NNSLITE_RET nnsliteHeapFSOpenNew(nnsliteHeapFS** heapFS, const char* filename, u32 bufsize, u32 pagesize, u32 numpages, u32 maxfiles)
{
	u32 structSize = sizeof(nnsliteHeapFS)+ (maxfiles-1)*sizeof(nnsliteHeapFile);
	*heapFS = (nnsliteHeapFS*)nnsliteMAlloc(structSize);
	strcpy((*heapFS)->filename, filename);
	(*heapFS)->structSize = structSize;
	(*heapFS)->magicNumber = NNSLITE_HEAPFS_MAGIC_NUMBER;
	(*heapFS)->fd = -1;
	(*heapFS)->defaultBufSize = bufsize;
	(*heapFS)->pageSize = pagesize;
	(*heapFS)->numPages = numpages;
	(*heapFS)->maxNumFiles = maxfiles;
	(*heapFS)->pageAllocationTable = (nnsliteHeapPageHeader*)nnsliteMAlloc(numpages*sizeof(nnsliteHeapPageHeader));
	(*heapFS)->pageStoreOffset =  structSize+numpages*sizeof(nnsliteHeapPageHeader);
	(*heapFS)->fd = open(filename, O_RDWR|O_CREAT|O_TRUNC|O_BINARY, S_IWRITE|S_IREAD);
	if ((*heapFS)->fd== -1)
	{
		assert(FALSE);
		return NNSLITE_RET_ERR_FILE_CANNOT_OPEN;
	}
	/* Zero the memories */
	memset((*heapFS)->pageAllocationTable, 0, numpages*sizeof(nnsliteHeapPageHeader));
	memset((*heapFS)->fileInfos, 0, maxfiles*sizeof(nnsliteHeapFile));
	return NNSLITE_RET_OK;
}

NNSLITE_RET nnsliteHeapFSSave(nnsliteHeapFS* heapFS)
{
	int fd = heapFS->fd;
	lseek(fd, 0, SEEK_SET);
	write(fd, heapFS, heapFS->structSize);
	write(fd, heapFS->pageAllocationTable, heapFS->numPages*sizeof(nnsliteHeapPageHeader));
	return NNSLITE_RET_OK;
}

NNSLITE_RET nnsliteHeapFSOpen(nnsliteHeapFS** heapFS, const char* filename)
{
	nnsliteHeapFS tempHeapFS;
	u32 PATsize;
	int fd;
	int rdsize;

	fd = open(filename,O_RDWR|O_BINARY);
	if (fd == -1)
	{
		assert(FALSE);
		return NNSLITE_RET_ERR_FILE_CANNOT_OPEN;
	}

	/* Pre-read the HeapFS structure into a temporary one, and get the "maxNumFiles" */ 
	lseek(fd, 0, SEEK_SET);
	rdsize = read(fd, &tempHeapFS, sizeof(nnsliteHeapFS));
	if (rdsize != sizeof(nnsliteHeapFS) || tempHeapFS.magicNumber != NNSLITE_HEAPFS_MAGIC_NUMBER)
	{
		/* This is not valid heap file system of this version's nnslite */
		close(fd);
		assert(false);
		return NNSLITE_RET_ERR_FILE_FORMAT;
	}

	/* Allocate the heapFS according to the "maxNumFiles" of the temporary heapFS */
	*heapFS = (nnsliteHeapFS*)nnsliteMAlloc(tempHeapFS.structSize);
	(*heapFS)->fd = fd;

	/* Re-read the heapFS */
	lseek(fd, 0, SEEK_SET);
	read(fd,*heapFS, tempHeapFS.structSize);

	/* Allocate the pageAllocationTable and read it */
	PATsize = (*heapFS)->numPages*sizeof(nnsliteHeapPageHeader);
	(*heapFS)->pageAllocationTable = (nnsliteHeapPageHeader*)nnsliteMAlloc(PATsize);
	read(fd,(*heapFS)->pageAllocationTable, PATsize);

	return NNSLITE_RET_OK;
}

NNSLITE_RET nnsliteHeapFSClose(nnsliteHeapFS* heapFS)
{
	NNSLITE_RET ret;

	/* save the heap file system firstly */
	ret = nnsliteHeapFSSave(heapFS);
	NNSLITE_ON_FAILED_RETURN(ret);

	/* close the file */
	if (heapFS->fd != -1)
		close(heapFS->fd);
	heapFS->fd = -1;

	/* free the memory */
	if (heapFS->pageAllocationTable != NULL)
		nnsliteMFree(heapFS->pageAllocationTable);
	heapFS->pageAllocationTable = NULL;
	nnsliteMFree(heapFS);
	return NNSLITE_RET_OK;
}

NNSLITE_RET nnsliteHeapFileOpenNew(nnsliteHeapFS* heapFS, nnsliteHeapFile** heapFile, u32 hfd)
{
	NNSLITE_RET ret;
	u32 freePageIndex;
	u32 i;

	ret = FindFreePage(heapFS, &freePageIndex);
	if (NNSLITE_RET_OK != ret)
	{
		assert(FALSE);
		return ret;
	}

	if (hfd  == U32_NULL) /* if the hfd is not given */
	{
		for (i=0; i<heapFS->maxNumFiles; i++)
		{
			if (heapFS->fileInfos[i].isUsed == FALSE)
			{
				*heapFile = &heapFS->fileInfos[i];
				(*heapFile)->isUsed = true;
				(*heapFile)->hfd = i;
				(*heapFile)->startPageIndex = freePageIndex;
				(*heapFile)->lastPageIndex = freePageIndex;
				(*heapFile)->heapFS = heapFS;
				(*heapFile)->capacity = 0;
				(*heapFile)->length = 0;
				break;
			}
		}

		if (i == heapFS->maxNumFiles)
		{
			assert(false);
			return NNSLITE_RET_ERR_DATAPAGE_OUTOFLIMIT;
		}
	}
	else  /* if the hfd is given and fixed by the user */
	{
		*heapFile = &heapFS->fileInfos[hfd];
		if ((*heapFile)->isUsed)
		{
			/* Delete the old file */
			ret = nnsliteHeapFileDelete(heapFS, hfd);
			NNSLITE_ON_FAILED_RETURN(ret);
		}
		(*heapFile)->isUsed = true;
		(*heapFile)->hfd = hfd;
		(*heapFile)->startPageIndex = freePageIndex;
		(*heapFile)->lastPageIndex = freePageIndex;
		(*heapFile)->heapFS = heapFS;
		(*heapFile)->capacity = 0;
		(*heapFile)->length = 0;
	}

	/* set the first page to be used */
	NNSLITE_PAGE_SET_USED(heapFS->pageAllocationTable[freePageIndex].state);

	return NNSLITE_RET_OK;
}

NNSLITE_RET nnsliteHeapFileOpen(nnsliteHeapFS* heapFS, u32 hfd, nnsliteHeapFile** heapFile)
{
	*heapFile = &heapFS->fileInfos[hfd];
	return NNSLITE_RET_OK;
}

NNSLITE_RET nnsliteHeapFileDelete(nnsliteHeapFS* heapFS, u32 hfd)
{
	u32 pageState;
	u32 pageIndex;
	nnsliteHeapFile* heapFile;
	heapFile = &heapFS->fileInfos[hfd];
	heapFile->isUsed = false;
	heapFile->hfd = 0;

	/* free all the files in the page allocation table */
	pageIndex = heapFile->startPageIndex;
	pageState = heapFS->pageAllocationTable[pageIndex].state;
	PageZero(heapFS, pageIndex);
	while(NNSLITE_PAGE_HAS_NEXT(pageState))
	{
		pageIndex = NNSLITE_PAGE_NEXT_PAGE(pageState);
		pageState = heapFS->pageAllocationTable[pageIndex].state;
		PageZero(heapFS, pageIndex);
	}

	return NNSLITE_RET_OK;
}

NNSLITE_RET nnsliteHeapFileExisted(nnsliteHeapFS* heapFS, u32 hfd, bool* isExisted)
{
	*isExisted = heapFS->fileInfos[hfd].isUsed;
	return NNSLITE_RET_OK;
}

NNSLITE_RET nnsliteHeapFileClose(nnsliteHeapFile* heapFile)
{
	return NNSLITE_RET_OK;
}

NNSLITE_RET nnsliteHeapFileAppend(nnsliteHeapFile* heapFile, const void* buf, u64 size)
{
	NNSLITE_RET ret;
	u8* byteBuf = (u8*)buf;
	nnsliteHeapFS* heapFS = heapFile->heapFS;
	nnsliteHeapPageHeader* PAT = heapFS->pageAllocationTable;
	u32 pageIndex = heapFile->lastPageIndex;
	u32 freeSizeOfPage = heapFS->pageSize - PAT[pageIndex].usedSize;
	u32 nextPageIndex;

	if (freeSizeOfPage == 0)
	{
		/* allocate new free page to this file */
		ret = FindFreePage(heapFS, &nextPageIndex);
		if (NNSLITE_FAILED(ret))
		{
			assert(FALSE);
			return ret;
		}
		NNSLITE_PAGE_SET_NEXTPAGE(PAT[pageIndex].state, nextPageIndex);
		NNSLITE_PAGE_SET_USED(PAT[nextPageIndex].state);
		pageIndex = nextPageIndex;
		freeSizeOfPage = heapFS->pageSize;
	}

	while (size > freeSizeOfPage)
	{
		/* fill the rest of page with the data */
		ret = PageWrite(heapFS, pageIndex, PAT[pageIndex].usedSize, byteBuf, freeSizeOfPage);
		NNSLITE_ON_FAILED_RETURN(ret);
		heapFile->length += freeSizeOfPage;
		byteBuf += freeSizeOfPage;
		size -= freeSizeOfPage;
		PAT[pageIndex].usedSize += freeSizeOfPage;

		/* allocate new free page to this file */
		ret = FindFreePage(heapFS, &nextPageIndex);
		NNSLITE_ON_FAILED_RETURN(ret);
		NNSLITE_PAGE_SET_NEXTPAGE(PAT[pageIndex].state, nextPageIndex);
		NNSLITE_PAGE_SET_USED(PAT[nextPageIndex].state);
		pageIndex = nextPageIndex;
		freeSizeOfPage = heapFS->pageSize;
	}

	if (size > 0)
	{
		/* fill the rest of page with the data */
		PageWrite(heapFS, pageIndex, PAT[pageIndex].usedSize, byteBuf, (u32)size);
		heapFile->length += size;
		PAT[pageIndex].usedSize += size;
	}

	heapFile->lastPageIndex = pageIndex;
	return NNSLITE_RET_OK;
}

NNSLITE_RET nnsliteHeapFileRead(nnsliteHeapFile* heapFile, u64 offset, void* buf, u64 size)
{
	NNSLITE_RET ret;
	u8* byteBuf = (u8*)buf;
	nnsliteHeapFS* heapFS = heapFile->heapFS;
	nnsliteHeapPageHeader* PAT = heapFS->pageAllocationTable;
	u32 pageIndexOfFile = (u32)(offset / heapFS->pageSize);
	u32 pageIndex = GetPageIndex(heapFile, pageIndexOfFile);
	u32 nextPageIndex;
	u32 offsetInPage = (u32)(offset - pageIndexOfFile*heapFS->pageSize);
	u32 sizeCanRead = PAT[pageIndex].usedSize - offsetInPage;

	while(size > sizeCanRead)
	{
		if (sizeCanRead == 0) /* No available data to read */
		{
			assert(FALSE);
			return NNSLITE_RET_ERR_NO_AVAILABLE;
		}

		/* read the rest of the page */
		ret = PageRead(heapFS, pageIndex, offsetInPage, byteBuf, sizeCanRead);
		NNSLITE_ON_FAILED_RETURN(ret);
		byteBuf += sizeCanRead;
		size -= sizeCanRead;

		/* move to the next page of the file */
		nextPageIndex = NNSLITE_PAGE_NEXT_PAGE(PAT[pageIndex].state);
		pageIndex = nextPageIndex;
		offsetInPage = 0;
		sizeCanRead = PAT[pageIndex].usedSize;
	}

	if (size > 0)
	{
		/* read the rest of the page */
		PageRead(heapFS, pageIndex, offsetInPage, byteBuf, (u32)size);
	}

	return NNSLITE_RET_OK;
}

static NNSLITE_RET FindFreePage(nnsliteHeapFS* heapFS, u32* pageindex)
{
	u32  i=0;
	nnsliteHeapPageHeader* PAT = heapFS->pageAllocationTable;
	for (i=0;i<heapFS->numPages; i++)
	{
		if (NNSLITE_PAGE_IS_USED(PAT[i].state) == FALSE)
		{
			*pageindex = i;
			return NNSLITE_RET_OK;
		}
	}
	return NNSLITE_RET_ERR_NO_MORE_SPACE;
}

static NNSLITE_RET PageWrite(nnsliteHeapFS* heapFS, u32 pageIndex, u32 offset, const void* buf, u32 size)
{
	u64 totalOffset = heapFS->pageStoreOffset;
	totalOffset += pageIndex * heapFS->pageSize + offset;
	lseek(heapFS->fd, totalOffset, SEEK_SET);
	write(heapFS->fd, buf, size);
	return NNSLITE_RET_OK;
}

static u32 PageRead(nnsliteHeapFS* heapFS, u32 pageIndex, u32 offset, void* buf, u32 size)
{
	u64 totalOffset = heapFS->pageStoreOffset;
	totalOffset += pageIndex * heapFS->pageSize + offset;
	lseek(heapFS->fd, totalOffset, SEEK_SET);
	read(heapFS->fd,buf, size);
	return NNSLITE_RET_OK;
}

static u32 GetPageIndex(nnsliteHeapFile* heapFile, u32 pageIndexOfFile)
{
	nnsliteHeapPageHeader* PAT = heapFile->heapFS->pageAllocationTable;
	u32 pageIndex = heapFile->startPageIndex;
	while(pageIndexOfFile > 0)
	{
		pageIndex = NNSLITE_PAGE_NEXT_PAGE(PAT[pageIndex].state);
		pageIndexOfFile--;
	}
	return pageIndex;
}

static void PageZero(nnsliteHeapFS* heapFS, u32 pageIndex)
{
	heapFS->pageAllocationTable[pageIndex].state = 0;
	heapFS->pageAllocationTable[pageIndex].usedSize = 0;
}


